using System;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

namespace ClientJIP
{
	
	
	
	public class Chat
	{
		//MESSAGE :
		const int OK_CONNECT = 100;
		const int OK_QUIT = 101;
		const int OK_AFK = 102;
		const int OK_NFK = 103;
		const int OK_LIST_AFK = 104;
		const int OK_LIST_NFK = 105;
		const int OK_LIST_PRIVATE = 106;
		const int OK_LIST_ASK_PRIVATE = 107;
		const int OK_NICK = 108;
		const int OK_TALK = 109;
		const int OK_ACCEPT = 110;
		const int OK_REJECT = 111;
		const int OK_CLOSE_TALK = 112;
		const int OK_MSG = 113;
		const int OK_CLOSE = 114;
		const int OK_ASK_SEND = 115 ;
		const int OK_ACCEPT_FILE = 116;
		const int OK_REJECT_FILE = 117 ;
		const int OK_SEND = 118;
		const int HELP_OK = 119;
		const int OK_DATE = 120;
		const int OK_PONG = 121;
		
		//MESSAGE ERROR :
		const int ERR_CONF_TALK_DENIED = 400;
		const int ERR_NO_SUCH_NICK = 401;
		const int ERR_NO_SEND_REQUEST = 403;
		const int ERR_UNKNOWN_COMMAND = 404;
		const int ERR_NO_NICK_NAME_GIVEN = 405;
		const int ERR_ERRONEUS_NICKNAME = 406;
		const int ERR_NICKNAME_IN_USE = 407;
		const int ERR_NICK_EGAL_PRESENT = 408;
		const int ERR_NEED_MORE_PARAMS = 409;
		const int ERR_NO_TALK_REQUEST = 410;
		const int ERR_NO_CURRENT_TALK = 411;
		const int ERR_UNAVAILABLE_FILE = 412;
		const int ERR_NICKNAME_FALSE = 413;
		const int ERR_TALK_EXIST = 414;
		
		//SIGNALS
		const int CONNECTED = 501;
		const int DISCONNECTED = 502;
		const int AWAY = 503;
		const int NICKNAME = 504;
		const int ASK_TALK = 505;
		const int ACCEPTED_TALK = 506;
		const int REFUSED_TALK = 507;
		const int MESSAGE_PUBLIC = 508;
		const int MESSAGE_PRIVATE = 509;
		const int CLOSE = 510;
		const int ASK_TRANSFERT = 511;
		const int ACCEPT_FILE = 512;
		const int REFUSED_FILE = 513;
		const int END_TALK = 514;
		const int ACTIVE = 515;
		const int FAIL_SEND = 516;
		const int RECEIVE = 517;
		const int PING = 518;
		
		
	
		public Dictionary<string, int> Connected{get; private set;}// -1 : nothink, other = page
		private Dictionary<string,string> FilesToSend;
		private Dictionary<string,string> FilesToReceive;
		private Dictionary<string,string> FilesToReceiveSize;
		
		
		
		// Mutex
		Mutex mutexList = new Mutex();
		
		// User name
		public String Pseudo{get; private set;}
		
		private String AskNewPseudo;
		
		// If /connect command successed
		public Boolean isConnected{get; private set;}
		public Boolean isNFK{get; private set;}
		
		private Boolean askListNfk;
		
		
		// Connection
		private NetworkChat NetChat;
		
		// File transfert
		private NetworkFile NetFile;
		
		// Controleur
		private CtrlChat Ctrl;
		
		
		
		public Chat (CtrlChat ctrl)
		{
			NetFile = new NetworkFile(this,System.IO.Directory.GetCurrentDirectory());
			NetChat = new NetworkChat(this);
			Ctrl = ctrl;
			
			
			//Default : Disconnected
			isConnected = false;
			isNFK = false;
			Pseudo = "";
			
			askListNfk = false;
			
			
			Connected = new Dictionary<string, int>();
			FilesToSend = new Dictionary<string, string>();
			FilesToReceive = new Dictionary<string, string>();
			FilesToReceiveSize = new Dictionary<string, string>();
		}
		
		
		
		
		
		/// <summary>
		/// Analyse the server response/message
		/// </summary>
		/// <param name="command">
		/// A <see cref="String"/>
		/// </param>
		private void AnalyseResponse(String command) {
			 int signal = 0;
			
			try {
			    signal = int.Parse(command.Split(' ')[0]);
			}
			catch {
				
				// for welcom
				Ctrl.WriteTextLog(command);
			    return;
			}
			
			Console.WriteLine(signal + " -> " + command);
	 
			
			if(isConnected && isNFK) {
				switch (signal) {
				    case OK_QUIT:
						QuitFromChat();
				        break;
				    case OK_NICK:
						ChangeNickPerso();
				        break;
				    case OK_AFK:
						SwitchToAfk();
				        break;
				    case OK_LIST_NFK:
						if(askListNfk) {
							Ctrl.WriteTextLog("Liste des membres disponibles : " + command.Remove(0, 4));
						}
						else{
							GetListUser(command.Remove(0, 4).Split(' '));
							WriteListUser();
						}
				        break;
					case OK_LIST_AFK:
						Ctrl.WriteTextLog("Liste des membres absents : " + command.Remove(0, 4));
						break;
					case OK_LIST_ASK_PRIVATE:
						Ctrl.WriteTextLog("Liste des membres absents privé : " + command.Remove(0, 4));
						break;
					case OK_LIST_PRIVATE:
						Ctrl.WriteTextLog("Liste des membres privé : " + command.Remove(0, 4));
						break;
					case OK_TALK:
						Ctrl.WriteTextPublic("La demande est bien reçue, en attente de réponse.", "");
						break;
					case OK_ACCEPT:
						break;
					case HELP_OK:
						Ctrl.WriteTextLog("Commandes disponibles : " + command.Remove(0, 4));
						break;
				    case CONNECTED:
						String pseudoNewUser = command.Split(' ')[1];
						newConnected(pseudoNewUser);
				        break;
				    case DISCONNECTED:
						String pseudoOldUser = command.Split(' ')[1];
						newDisconnected(pseudoOldUser, command.Remove(0, 4 + pseudoOldUser.Length));
				        break;
				    case ACTIVE:
						Actif(command.Split(' ')[1]);
				        break;
				    case AWAY:
						Away(command.Split(' ')[1]);
				        break;
				    case ASK_TALK:
						AskTalk(command.Split(' ')[1]);
				        break;
					case ACCEPTED_TALK:
						AcceptTalk(command.Split(' ')[1], Ctrl.GetNbPage());
						break;
					case REFUSED_TALK:
						RefuseTalk(command.Split(' ')[1]);
						break;
					
					case ASK_TRANSFERT:
						String pseudo = command.Split(' ')[1];
						String fileReady = command.Split(' ')[2];
						String fileSize = command.Split(' ')[3];
					
						FilesToReceive.Add(pseudo,fileReady);
						FilesToReceiveSize.Add(pseudo,fileSize);
					
						Ctrl.WriteTextPrivate("On souhaite vous envoyer le fichier : " + fileReady,Pseudo,pseudo);
						
						Ctrl.AskChooseFileTransfert(pseudo);
						break;
					case ACCEPT_FILE:
						String fileAccept = command.Split(' ')[2];
						String ipAddress = command.Split(' ')[3];
						String portAddress = command.Split(' ')[4];
					
						//Démarre l'envoi
						NetFile.SendFile(ipAddress,portAddress,FilesToSend[fileAccept]);
						break;
					case REFUSED_FILE:
						String pseudoRefuse = command.Split(' ')[1];
						String fileRefuse = command.Split(' ')[2];
						Ctrl.WriteTextPublic(pseudoRefuse + " refuse l'envoie du fichier : " + fileRefuse, "");
						break;
				    case MESSAGE_PUBLIC:
						String pseudoSender = command.Split(' ')[1];
						if(pseudoSender != Pseudo)
							Ctrl.WriteTextPublic(command.Remove(0, 4 + pseudoSender.Length), pseudoSender);
				        break;
				    case MESSAGE_PRIVATE:
						String pseudoPrivate = command.Split(' ')[1];
						MessagePrivate(pseudoPrivate, command.Remove(0, 4 + pseudoPrivate.Length), pseudoPrivate);
				        break;
				    case NICKNAME:
						String[] tabTmp = command.Split(' ');
						if(tabTmp[2] != Pseudo)
							ChangeNick(tabTmp[1], tabTmp[2]);
				        break;
				    case PING:
						WriteLine("/PONG");
				        break;
					
				    case END_TALK:
						CloseTalk(command.Split(' ')[1]);
				        break;
				    case CLOSE:
						CloseTalk(command.Split(' ')[1]);
				        break;
				    case ERR_UNKNOWN_COMMAND:
						Unknown();
				        break;
				    case ERR_NICKNAME_IN_USE:
						Ctrl.WriteTextPublic("Le pseudo est déjà utilisé", "");
						break;
				    case ERR_ERRONEUS_NICKNAME:
						Ctrl.WriteTextPublic("Le pseudo est incorrect", "");
						break;
				    case ERR_NICKNAME_FALSE:
						Ctrl.WriteTextPublic("Le pseudo est trop long ou est une commande serveur", "");
						break;
				}
			}
			else if(!isConnected) {
				switch (signal) {
				    case OK_CONNECT:
						ConnectToChat();
						break;
				    case ERR_NICKNAME_IN_USE:
						Ctrl.WriteTextLog("Le pseudo est déjà utilisé");
						break;
				    case ERR_ERRONEUS_NICKNAME:
						Ctrl.WriteTextLog("Le pseudo est incorrect");
						break;
				    case ERR_NICKNAME_FALSE:
						Ctrl.WriteTextLog("Le pseudo est trop long ou est une commande serveur");
						break;
				}
			}
			
			else if(!isNFK) {
				switch (signal) {
					case OK_NFK:
						SwitchToNfk();
						break;
				    case OK_QUIT:
						QuitFromChat();
				        break;
				}
			}
		}
		
		
		
		
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		//																							Serveur
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
		
		/// <summary>
		/// Read a line from network
		/// </summary>
		public void ReadLine() {
			if(NetChat.IsDataAvailable())
				AnalyseResponse(NetChat.ReadLine());
		}
				
		/// <summary>
		/// Write a line in network
		/// </summary>
		/// <param name="message">
		/// A <see cref="System.String"/>
		/// </param>
		public void WriteLine(string message)
		{
			NetChat.WriteLine(message);
		}
		
		
		
		
		public bool ConnectToServeur(string host) {
			if(NetChat.Running)
				NetChat.Stop();
			
			return NetChat.Start(host, 8080);
		}
		
		public bool DisconnectServeur() {
			Ctrl.writeToServeur("/QUIT");
			QuitFromChat();
			return NetChat.Stop();
		}
		
		
		public void QuitFromChat() {
		
			isConnected = false;
			isNFK = false;
			Pseudo = "";
			
			var talkWith = (from entry in Connected where entry.Value != -1 select entry.Value);
			
			foreach(int tmp in talkWith) {
				Ctrl.DeletePageTalk(tmp);
			}
			
			Ctrl.QuitFromChat();
			Connected.Clear();
		}
		
		
		
		
		
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		//																							From Ctrl
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
		
		
		
		public virtual bool SendCommand(string message, int page)
		{
			message = message.Trim();
			
			
			//On vérifie que le client est bien connecté
			if(!NetChat.Running)
				return false;
			
			
			if(message.Trim() != "") {	
				
				String[] tab = message.Split(' ');	
	
				if(!isConnected) {
					if(tab[0].ToUpper().Equals("/CONNECT"))
						Pseudo = tab[1];
						
					// if there are not a command
					else if(tab.Length == 1) {
						Pseudo = message;
						message = "/CONNECT " + message;
					}
				}
				else if(!isNFK)
					message = "/NFK";
				else{
					if(tab[0].ToUpper() == "/NICK")
						AskNewPseudo = tab[1];
						
					// if there are not a command
					else if(tab[0][0] != '/') {
						if(page == 1) {
	        				Ctrl.WriteTextPublic(message, Pseudo);
							message = "/MSG PUBLIC " + message;
						}
						
						else if(page > 1) {
							
							foreach(var test in Connected) {
								Console.WriteLine("Value : " + test.Value + " | Key : " + test.Key);	
							}
							
							string receive = (string)(from entry in Connected where entry.Value == page select entry.Key).First();
							
	        				MessagePrivate(Pseudo, message, receive);
							
							
							
							message = "/MSG " + receive + " " + message;
						}
					}
					else if(tab[0].ToUpper() == "/LIST_NFK")
						askListNfk = true;
				}
			}
			
			WriteLine(message);
			
			return true;
		}
				
		/// <summary>
		/// Execute connected event steps for client
		/// </summary>
		public void ConnectToChat() {
			isConnected = true;
			isNFK = true;
			
			Ctrl.ConnectedToChat();
				
			WriteLine("/HELP");
			WriteLine("/LIST_NFK");
		}
		
		public void FilesToSendClient(String fileName, String pathFileName, String tmpPseudo, long fileSize)
		{
			NetChat.WriteLine("/ASK_SEND " + tmpPseudo + " " + fileName.Replace(' ','_') + " " + fileSize);
			FilesToSend.Add(fileName.Replace(' ','_'),pathFileName);	
		}
		
		public void AcceptFile(String pseudo)
		{
			string s = System.Net.Dns.GetHostName();
			System.Net.IPHostEntry h = System.Net.Dns.GetHostEntry(s);
			NetChat.WriteLine("/ACCEPT_FILE " + pseudo + " " + FilesToReceive[pseudo] + " " + h.AddressList[0] + " " + NetFile.ReceiveFile(FilesToReceiveSize[pseudo],FilesToReceive[pseudo]));
			FilesToReceiveSize.Remove(pseudo);
			FilesToReceive.Remove(pseudo);
		}
		
		public void RefuseFile(String pseudo)
		{
			NetChat.WriteLine("/REJECT_FILE " + pseudo + " " + FilesToReceive[pseudo]);
		}
		
		
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		//																							Commands
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
		
		private void Unknown() {
	        Ctrl.WriteTextLog("Commande inconnue.");
		}

		
		private void newConnected(String pseudoNewUser) {
			if(pseudoNewUser != Pseudo) {
		        Ctrl.WriteTextPublic("Nouveau connecté : " + pseudoNewUser +".", "");
				
				AddUserList(pseudoNewUser);
				WriteListUser();
			}
		}
		
		private void newDisconnected(String pseudOldUser, String reason) {
			int page = Connected[pseudOldUser];
			
			if(page != -1)
				Ctrl.DeletePageTalk(page);
			
			if(reason == String.Empty)
	        	Ctrl.WriteTextPublic(pseudOldUser +" est deconnecté", "");
			else
	        	Ctrl.WriteTextPublic(pseudOldUser +" est deconnecté (" + reason + ")", "");
			
	
			RemoveUserList(pseudOldUser);
			WriteListUser();
		}
		
		
		/// <summary>
		/// Execute pseudo change event steps for client
		/// </summary>
		private void ChangeNickPerso() {
			Pseudo = AskNewPseudo;
	        Ctrl.WriteTextPublic("Vous avez changé votre pseudo en " + Pseudo + ".", "");
		}
		
				
		public void ChangeNick(String oldPseudo, String newPseudo) {
			Ctrl.ChangeNick(oldPseudo, newPseudo);
			
			Connected.Add(newPseudo, Connected[oldPseudo]);
			Connected.Remove(oldPseudo);
		}
			
		public void SwitchToAfk() {
			isNFK = false;
			
			var tmp = from entry in Connected where entry.Value != -1 select entry.Value;
			
			foreach(int index in tmp) {
				Ctrl.DeletePageTalk(index);
			}
			Connected.Clear();
			
			Ctrl.SwitchToAfk();
		}
		
		private void SwitchToNfk() {
			isNFK = true;
			
			Ctrl.SwitchToNfk();
		}
				
		
		public void WriteListUser() {
			mutexList.WaitOne();
			Ctrl.WriteListUser(Connected);
			mutexList.ReleaseMutex();
		}
		
		
		void Actif(String pseudoActif) {
	        Ctrl.WriteTextPublic(pseudoActif + " est à présent en ligne.", "");
			
			AddUserList(pseudoActif);
			
				
			WriteListUser();
		}
		
		void Away(String pseudoInactif) {
			if(Connected[pseudoInactif] != -1)
				Ctrl.DeletePageTalk(Connected[pseudoInactif]);
			
	        Ctrl.WriteTextPublic(pseudoInactif + " est absent.", "");
			
			RemoveUserList(pseudoInactif);
			
				
			WriteListUser();
		}
		
		private void AskTalk(string pseudoAsker) {
        	Ctrl.WriteTextPublic(pseudoAsker + " demande une conversation privée.", "");
			Ctrl.AskChooseConversation(pseudoAsker);
		}
		
		
		
		public void AcceptTalk(string pseudoTalker, int page) {
			Connected[pseudoTalker] = page;
			
			Ctrl.AcceptTalk(pseudoTalker);
		}
		
		private void RefuseTalk(string pseudoTalker) {
	        Ctrl.WriteTextPublic(pseudoTalker + " refuse d'avoir une conversation privée.", "");
		}
					
		private void MessagePrivate(String pseudo_write, String message, string pseudoPage) {
	        Ctrl.WriteTextPrivate(message, pseudo_write, pseudoPage);
		}
		
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		//																							Tools
		// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
		
		
		private void AddUserList(string userPseudo) {
			mutexList.WaitOne();
			
			if(!Connected.Keys.Contains(userPseudo)) {
				if(Pseudo != userPseudo)
					Connected.Add(userPseudo, -1);
			}
			else
				Connected[userPseudo] = -1;
			
			mutexList.ReleaseMutex();
		}
			void RemoveUserList(string userPseudo) {
			mutexList.WaitOne();
			
			if(Connected.Keys.Contains(userPseudo) && Pseudo != userPseudo)
				Connected.Remove(userPseudo);
			
			mutexList.ReleaseMutex();
		}
		
		private void GetListUser(String[] users) {
			mutexList.WaitOne();
			
			Connected.Clear();
			
	        foreach(String user in users) {
					AddUserList(user);
			}
			
			mutexList.ReleaseMutex();
		}
		
		
		public void CloseTalk(string pseudo) {
			Ctrl.DeletePageTalk(Connected[pseudo]);
			Connected[pseudo] = -1;
		}
		
	}
}

